# 简单Web服务器实现
import socket
import os
from datetime import datetime

WEB_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web')

# MIME类型映射
MIME_TYPES = {
    '.html': 'text/html',
    '.jpg': 'image/jpeg',
    '.png': 'image/png',
}


def get_mime_type(file_path):
    """根据文件扩展名获取MIME类型"""
    ext = os.path.splitext(file_path)[1].lower()
    return MIME_TYPES.get(ext, 'application/octet-stream')


def read_file(file_path):
    """安全读取文件内容，防止目录遍历攻击"""
    try:
        # 确保请求的文件在WEB_ROOT目录内
        abs_path = os.path.abspath(file_path)
        if not abs_path.startswith(WEB_ROOT):
            return None, 403  # 禁止访问

        if not os.path.exists(abs_path) or not os.path.isfile(abs_path):
            return None, 404  # 文件不存在

        # 文本文件使用UTF-8编码读取
        if file_path.endswith(('.html', '.htm', '.txt', '.css', '.js')):
            with open(abs_path, 'r', encoding='utf-8') as file:
                return file.read(), 200
        # 二进制文件使用二进制模式读取
        else:
            with open(abs_path, 'rb') as file:
                return file.read(), 200

    except Exception as e:
        print(f"读取文件出错: {e}")
        return None, 500  # 服务器内部错误


def generate_error_response(status_code):
    """生成错误响应"""
    error_messages = {
        400: "Bad Request",
        403: "Forbidden",
        404: "Not Found",
        500: "Internal Server Error"
    }

    message = error_messages.get(status_code, "Error")
    content = f"""<!DOCTYPE html>
<html>
<head>
    <title>{status_code} {message}</title>
</head>
<body>
    <h1>{status_code} {message}</h1>
</body>
</html>"""

    response_header = f"HTTP/1.1 {status_code} {message}\r\n"
    response_header += "Content-Type: text/html; charset=utf-8\r\n"
    response_header += f"Content-Length: {len(content)}\r\n"
    response_header += "Connection: close\r\n\r\n"

    return (response_header + content).encode()


def run_web_server():
    # 创建TCP套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 绑定地址和端口
    server_address = ('localhost', 8080)
    server_socket.bind(server_address)

    # 开始监听
    server_socket.listen(5)
    print(f"Web服务器启动，监听地址: {server_address}")
    print(f"Web根目录: {WEB_ROOT}")

    while True:
        # 接受客户端连接
        connection_socket, client_address = server_socket.accept()
        print(f"接受来自 {client_address} 的连接")

        try:
            # 接收客户端请求
            request = connection_socket.recv(1024).decode()
            if not request:
                continue

            # 解析HTTP请求行
            request_lines = request.split('\r\n')
            request_line = request_lines[0]

            try:
                method, path, version = request_line.split(' ')
            except ValueError:
                # 无效请求行
                connection_socket.sendall(generate_error_response(400))
                continue

            # 处理请求路径（防止目录遍历攻击）
            if path == '/':
                file_path = os.path.join(WEB_ROOT, 'index.html')
            else:
                # 安全处理路径，防止../攻击
                safe_path = path.strip('/').replace('../', '')
                file_path = os.path.join(WEB_ROOT, safe_path)

            # 读取文件内容
            content, status_code = read_file(file_path)

            if status_code != 200:
                # 错误处理
                response = generate_error_response(status_code)
            else:
                # 准备HTTP响应头
                response_header = f"HTTP/1.1 200 OK\r\n"
                response_header += f"Date: {datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')}\r\n"
                response_header += f"Content-Type: {get_mime_type(file_path)}\r\n"

                # 根据内容类型决定如何发送响应
                if isinstance(content, str):
                    # 文本内容
                    response_header += f"Content-Length: {len(content.encode('utf-8'))}\r\n"
                    response_header += "Connection: close\r\n\r\n"
                    response = (response_header + content).encode('utf-8')
                else:
                    # 二进制内容
                    response_header += f"Content-Length: {len(content)}\r\n"
                    response_header += "Connection: close\r\n\r\n"
                    response = response_header.encode('utf-8') + content

            # 发送响应
            connection_socket.sendall(response)

        except Exception as e:
            print(f"处理请求时出错: {e}")
            connection_socket.sendall(generate_error_response(500))
        finally:
            # 关闭连接
            connection_socket.close()


if __name__ == "__main__":
    run_web_server()
